home *** CD-ROM | disk | FTP | other *** search
/ Languguage OS 2 / Languguage OS II Version 10-94 (Knowledge Media)(1994).ISO / gnu / cvs-1_3.lha / cvs-1.3 / src / history.c < prev    next >
C/C++ Source or Header  |  1992-04-09  |  39KB  |  1,374 lines

  1. /*
  2.  *
  3.  *    You may distribute under the terms of the GNU General Public License
  4.  *    as specified in the README file that comes with the CVS 1.0 kit.
  5.  *
  6.  * **************** History of Users and Module ****************
  7.  *
  8.  * LOGGING:  Append record to "${CVSROOT}/CVSROOTADM/CVSROOTADM_HISTORY".
  9.  *
  10.  * On For each Tag, Add, Checkout, Commit, Update or Release command,
  11.  * one line of text is written to a History log.
  12.  *
  13.  *    X date | user | CurDir | special | rev(s) | argument '\n'
  14.  *
  15.  * where: [The spaces in the example line above are not in the history file.]
  16.  *
  17.  *  X        is a single character showing the type of event:
  18.  *        T    "Tag" cmd.
  19.  *        O    "Checkout" cmd.
  20.  *        F    "Release" cmd.
  21.  *        W    "Update" cmd - No User file, Remove from Entries file.
  22.  *        U    "Update" cmd - File was checked out over User file.
  23.  *        G    "Update" cmd - File was merged successfully.
  24.  *        C    "Update" cmd - File was merged and shows overlaps.
  25.  *        M    "Commit" cmd - "Modified" file.
  26.  *        A    "Commit" cmd - "Added" file.
  27.  *        R    "Commit" cmd - "Removed" file.
  28.  *
  29.  *  date    is a fixed length 8-char hex representation of a Unix time_t.
  30.  *        [Starting here, variable fields are delimited by '|' chars.]
  31.  *
  32.  *  user    is the username of the person who typed the command.
  33.  *
  34.  *  CurDir    The directory where the action occurred.  This should be the
  35.  *        absolute path of the directory which is at the same level as
  36.  *        the "Repository" field (for W,U,G,C & M,A,R).
  37.  *
  38.  *  Repository    For record types [W,U,G,C,M,A,R] this field holds the
  39.  *        repository read from the administrative data where the
  40.  *        command was typed.
  41.  *        T    "A" --> New Tag, "D" --> Delete Tag
  42.  *            Otherwise it is the Tag or Date to modify.
  43.  *        O,F    A "" (null field)
  44.  *
  45.  *  rev(s)    Revision number or tag.
  46.  *        T    The Tag to apply.
  47.  *        O    The Tag or Date, if specified, else "" (null field).
  48.  *        F    "" (null field)
  49.  *        W    The Tag or Date, if specified, else "" (null field).
  50.  *        U    The Revision checked out over the User file.
  51.  *        G,C    The Revision(s) involved in merge.
  52.  *        M,A,R    RCS Revision affected.
  53.  *
  54.  *  argument    The module (for [TOUF]) or file (for [WUGCMAR]) affected.
  55.  *
  56.  *
  57.  *** Report categories: "User" and "Since" modifiers apply to all reports.
  58.  *            [For "sort" ordering see the "sort_order" routine.]
  59.  *
  60.  *   Extract list of record types
  61.  *
  62.  *    -e, -x [TOFWUGCMAR]
  63.  *
  64.  *        Extracted records are simply printed, No analysis is performed.
  65.  *        All "field" modifiers apply.  -e chooses all types.
  66.  *
  67.  *   Checked 'O'ut modules
  68.  *
  69.  *    -o, -w
  70.  *        Checked out modules.  'F' and 'O' records are examined and if
  71.  *        the last record for a repository/file is an 'O', a line is
  72.  *        printed.  "-w" forces the "working dir" to be used in the
  73.  *        comparison instead of the repository.
  74.  *
  75.  *   Committed (Modified) files
  76.  *
  77.  *    -c, -l, -w
  78.  *        All 'M'odified, 'A'dded and 'R'emoved records are examined.
  79.  *        "Field" modifiers apply.  -l forces a sort by file within user
  80.  *        and shows only the last modifier.  -w works as in Checkout.
  81.  *
  82.  *        Warning: Be careful with what you infer from the output of
  83.  *             "cvs hi -c -l".  It means the last time *you*
  84.  *             changed the file, not the list of files for which
  85.  *             you were the last changer!!!
  86.  *
  87.  *   Module history for named modules.
  88.  *    -m module, -l
  89.  *
  90.  *        This is special.  If one or more modules are specified, the
  91.  *        module names are remembered and the files making up the
  92.  *        modules are remembered.  Only records matching exactly those
  93.  *        files and repositories are shown.  Sorting by "module", then
  94.  *        filename, is implied.  If -l ("last modified") is specified,
  95.  *        then "update" records (types WUCG), tag and release records
  96.  *        are ignored and the last (by date) "modified" record.
  97.  *
  98.  *   TAG history
  99.  *
  100.  *    -T    All Tag records are displayed.
  101.  *
  102.  *** Modifiers.
  103.  *
  104.  *   Since ...        [All records contain a timestamp, so any report
  105.  *             category can be limited by date.]
  106.  *
  107.  *    -D date        - The "date" is parsed into a Unix "time_t" and
  108.  *              records with an earlier time stamp are ignored.
  109.  *    -r rev/tag    - A "rev" begins with a digit.  A "tag" does not.  If
  110.  *              you use this option, every file is searched for the
  111.  *              indicated rev/tag.
  112.  *    -t tag        - The "tag" is searched for in the history file and no
  113.  *              record is displayed before the tag is found.  An
  114.  *              error is printed if the tag is never found.
  115.  *    -b string    - Records are printed only back to the last reference
  116.  *              to the string in the "module", "file" or
  117.  *              "repository" fields.
  118.  *
  119.  *   Field Selections    [Simple comparisons on existing fields.  All field
  120.  *             selections are repeatable.]
  121.  *
  122.  *    -a        - All users.
  123.  *    -u user        - If no user is given and '-a' is not given, only
  124.  *              records for the user typing the command are shown.
  125.  *              ==> If -a or -u is not specified, just use "self".
  126.  *
  127.  *    -f filematch    - Only records in which the "file" field contains the
  128.  *              string "filematch" are considered.
  129.  *
  130.  *    -p repository    - Only records in which the "repository" string is a
  131.  *              prefix of the "repos" field are considered.
  132.  *
  133.  *    -m modulename    - Only records which contain "modulename" in the
  134.  *              "module" field are considered.
  135.  *
  136.  *
  137.  * EXAMPLES: ("cvs history", "cvs his" or "cvs hi")
  138.  *
  139.  *** Checked out files for username.  (default self, e.g. "dgg")
  140.  *    cvs hi            [equivalent to: "cvs hi -o -u dgg"]
  141.  *    cvs hi -u user        [equivalent to: "cvs hi -o -u user"]
  142.  *    cvs hi -o        [equivalent to: "cvs hi -o -u dgg"]
  143.  *
  144.  *** Committed (modified) files from the beginning of the file.
  145.  *    cvs hi -c [-u user]
  146.  *
  147.  *** Committed (modified) files since Midnight, January 1, 1990:
  148.  *    cvs hi -c -D 'Jan 1 1990' [-u user]
  149.  *
  150.  *** Committed (modified) files since tag "TAG" was stored in the history file:
  151.  *    cvs hi -c -t TAG [-u user]
  152.  *
  153.  *** Committed (modified) files since tag "TAG" was placed on the files:
  154.  *    cvs hi -c -r TAG [-u user]
  155.  *
  156.  *** Who last committed file/repository X?
  157.  *    cvs hi -c -l -[fp] X
  158.  *
  159.  *** Modified files since tag/date/file/repos?
  160.  *    cvs hi -c {-r TAG | -D Date | -b string}
  161.  *
  162.  *** Tag history
  163.  *    cvs hi -T
  164.  *
  165.  *** History of file/repository/module X.
  166.  *    cvs hi -[fpn] X
  167.  *
  168.  *** History of user "user".
  169.  *    cvs hi -e -u user
  170.  *
  171.  *** Dump (eXtract) specified record types
  172.  *    cvs hi -x [TOFWUGCMAR]
  173.  *
  174.  *
  175.  * FUTURE:        J[Join], I[Import]  (Not currently implemented.)
  176.  *
  177.  */
  178.  
  179. #include "cvs.h"
  180.  
  181. #ifndef lint
  182. static char rcsid[] = "@(#)history.c 1.31 92/04/10";
  183. #endif
  184.  
  185. static struct hrec
  186. {
  187.     char *type;        /* Type of record (In history record) */
  188.     char *user;        /* Username (In history record) */
  189.     char *dir;        /* "Compressed" Working dir (In history record) */
  190.     char *repos;    /* (Tag is special.) Repository (In history record) */
  191.     char *rev;        /* Revision affected (In history record) */
  192.     char *file;        /* Filename (In history record) */
  193.     char *end;        /* Ptr into repository to copy at end of workdir */
  194.     char *mod;        /* The module within which the file is contained */
  195.     time_t date;    /* Calculated from date stored in record */
  196.     int idx;        /* Index of record, for "stable" sort. */
  197. } *hrec_head;
  198.  
  199.  
  200. #if __STDC__
  201. static char *fill_hrec (char *line, struct hrec * hr);
  202. static int accept_hrec (struct hrec * hr, struct hrec * lr);
  203. static int select_hrec (struct hrec * hr);
  204. static int sort_order (CONST PTR l, CONST PTR r);
  205. static int within (char *find, char *string);
  206. static time_t date_and_time (char *date_str);
  207. static void expand_modules (void);
  208. static void read_hrecs (char *fname);
  209. static void report_hrecs (void);
  210. static void save_file (char *dir, char *name, char *module);
  211. static void save_module (char *module);
  212. static void save_user (char *name);
  213. #else
  214. static int sort_order ();
  215. static time_t date_and_time ();
  216. static void save_user ();
  217. static void save_file ();
  218. static void save_module ();
  219. static void expand_modules ();
  220. static char *fill_hrec ();
  221. static void read_hrecs ();
  222. static int within ();
  223. static int select_hrec ();
  224. static void report_hrecs ();
  225. static int accept_hrec ();
  226. #endif                /* __STDC__ */
  227.  
  228. #define ALL_REC_TYPES "TOFWUCGMAR"
  229. #define USER_INCREMENT    2
  230. #define FILE_INCREMENT    128
  231. #define MODULE_INCREMENT 5
  232. #define HREC_INCREMENT    128
  233.  
  234. static short report_count;
  235.  
  236. static short extract;
  237. static short v_checkout;
  238. static short modified;
  239. static short tag_report;
  240. static short module_report;
  241. static short working;
  242. static short last_entry;
  243. static short all_users;
  244.  
  245. static short user_sort;
  246. static short repos_sort;
  247. static short file_sort;
  248. static short module_sort;
  249.  
  250. static time_t since_date;
  251. static char since_rev[20];    /* Maxrev ~= 99.99.99.999 */
  252. static char since_tag[64];
  253. static struct hrec *last_since_tag;
  254. static char backto[128];
  255. static struct hrec *last_backto;
  256. static char rec_types[20];
  257.  
  258. static int hrec_count;
  259. static int hrec_max;
  260.  
  261. static char **user_list;    /* Ptr to array of ptrs to user names */
  262. static int user_max;        /* Number of elements allocated */
  263. static int user_count;        /* Number of elements used */
  264.  
  265. static struct file_list_str
  266. {
  267.     char *l_file;
  268.     char *l_module;
  269. } *file_list;            /* Ptr to array file name structs */
  270. static int file_max;        /* Number of elements allocated */
  271. static int file_count;        /* Number of elements used */
  272.  
  273. static char **mod_list;        /* Ptr to array of ptrs to module names */
  274. static int mod_max;        /* Number of elements allocated */
  275. static int mod_count;        /* Number of elements used */
  276.  
  277. static int histsize;
  278. static char *histdata;
  279. static char *histfile;        /* Ptr to the history file name */
  280.  
  281. static char *history_usg[] =
  282. {
  283.     "Usage: %s %s [-report] [-flags] [-options args] [files...]\n\n",
  284.     "   Reports:\n",
  285.     "        -T              Produce report on all TAGs\n",
  286.     "        -c              Committed (Modified) files\n",
  287.     "        -o              Checked out modules\n",
  288.     "        -m <module>     Look for specified module (repeatable)\n",
  289.     "        -x [TOFWUCGMAR] Extract by record type\n",
  290.     "   Flags:\n",
  291.     "        -a              All users (Default is self)\n",
  292.     "        -e              Everything (same as -x, but all record types)\n",
  293.     "        -l              Last modified (committed or modified report)\n",
  294.     "        -w              Working directory must match\n",
  295.     "   Options:\n",
  296.     "        -D <date>       Since date (Many formats)\n",
  297.     "        -b <str>        Back to record with str in module/file/repos field\n",
  298.     "        -f <file>       Specified file (same as command line) (repeatable)\n",
  299.     "        -n <modulename> In module (repeatable)\n",
  300.     "        -p <repos>      In repository (repeatable)\n",
  301.     "        -r <rev/tag>    Since rev or tag (looks inside RCS files!)\n",
  302.     "        -t <tag>        Since tag record placed in history file (by anyone).\n",
  303.     "        -u <user>       For user name (repeatable)\n",
  304.     NULL};
  305.  
  306. /* Sort routine for qsort:
  307.    - If a user is selected at all, sort it first. User-within-file is useless.
  308.    - If a module was selected explicitly, sort next on module.
  309.    - Then sort by file.  "File" is "repository/file" unless "working" is set,
  310.      then it is "workdir/file".  (Revision order should always track date.)
  311.    - Always sort timestamp last.
  312. */
  313. static int
  314. sort_order (l, r)
  315.     CONST PTR l;
  316.     CONST PTR r;
  317. {
  318.     int i;
  319.     CONST struct hrec *left = (CONST struct hrec *) l;
  320.     CONST struct hrec *right = (CONST struct hrec *) r;
  321.  
  322.     if (user_sort)    /* If Sort by username, compare users */
  323.     {
  324.     if ((i = strcmp (left->user, right->user)) != 0)
  325.         return (i);
  326.     }
  327.     if (module_sort)    /* If sort by modules, compare module names */
  328.     {
  329.     if (left->mod && right->mod)
  330.         if ((i = strcmp (left->mod, right->mod)) != 0)
  331.         return (i);
  332.     }
  333.     if (repos_sort)    /* If sort by repository, compare them. */
  334.     {
  335.     if ((i = strcmp (left->repos, right->repos)) != 0)
  336.         return (i);
  337.     }
  338.     if (file_sort)    /* If sort by filename, compare files, NOT dirs. */
  339.     {
  340.     if ((i = strcmp (left->file, right->file)) != 0)
  341.         return (i);
  342.  
  343.     if (working)
  344.     {
  345.         if ((i = strcmp (left->dir, right->dir)) != 0)
  346.         return (i);
  347.  
  348.         if ((i = strcmp (left->end, right->end)) != 0)
  349.         return (i);
  350.     }
  351.     }
  352.  
  353.     /*
  354.      * By default, sort by date, time
  355.      * XXX: This fails after 2030 when date slides into sign bit
  356.      */
  357.     if ((i = ((long) (left->date) - (long) (right->date))) != 0)
  358.     return (i);
  359.  
  360.     /* For matching dates, keep the sort stable by using record index */
  361.     return (left->idx - right->idx);
  362. }
  363.  
  364. static time_t
  365. date_and_time (date_str)
  366.     char *date_str;
  367. {
  368.     time_t t;
  369.  
  370.     t = get_date (date_str, (struct timeb *) NULL);
  371.     if (t == (time_t) - 1)
  372.     error (1, 0, "Can't parse date/time: %s", date_str);
  373.     return (t);
  374. }
  375.  
  376. int
  377. history (argc, argv)
  378.     int argc;
  379.     char **argv;
  380. {
  381.     int i, c;
  382.     char fname[PATH_MAX];
  383.  
  384.     if (argc == -1)
  385.     usage (history_usg);
  386.  
  387.     optind = 1;
  388.     while ((c = gnu_getopt (argc, argv, "Tacelow?D:b:f:m:n:p:r:t:u:x:X:")) != -1)
  389.     {
  390.     switch (c)
  391.     {
  392.         case 'T':            /* Tag list */
  393.         report_count++;
  394.         tag_report++;
  395.         break;
  396.         case 'a':            /* For all usernames */
  397.         all_users++;
  398.         break;
  399.         case 'c':
  400.         report_count++;
  401.         modified = 1;
  402.         break;
  403.         case 'e':
  404.         report_count++;
  405.         extract++;
  406.         (void) strcpy (rec_types, ALL_REC_TYPES);
  407.         break;
  408.         case 'l':            /* Find Last file record */
  409.         last_entry = 1;
  410.         break;
  411.         case 'o':
  412.         report_count++;
  413.         v_checkout = 1;
  414.         break;
  415.         case 'w':            /* Match Working Dir (CurDir) fields */
  416.         working = 1;
  417.         break;
  418.         case 'X':            /* Undocumented debugging flag */
  419.         histfile = optarg;
  420.         break;
  421.         case 'D':            /* Since specified date */
  422.         if (*since_rev || *since_tag || *backto)
  423.         {
  424.             error (0, 0, "date overriding rev/tag/backto");
  425.             *since_rev = *since_tag = *backto = '\0';
  426.         }
  427.         since_date = date_and_time (optarg);
  428.         break;
  429.         case 'b':            /* Since specified file/Repos */
  430.         if (since_date || *since_rev || *since_tag)
  431.         {
  432.             error (0, 0, "backto overriding date/rev/tag");
  433.             *since_rev = *since_tag = '\0';
  434.             since_date = 0;
  435.         }
  436.         if (strlen (optarg) >= sizeof (backto))
  437.         {
  438.             error (0, 0, "backto truncated to %d bytes",
  439.                sizeof (backto) - 1);
  440.             optarg[sizeof (backto) - 1] = '\0';
  441.         }
  442.         (void) strcpy (backto, optarg);
  443.         break;
  444.         case 'f':            /* For specified file */
  445.         save_file ("", optarg, (char *) NULL);
  446.         break;
  447.         case 'm':            /* Full module report */
  448.         report_count++;
  449.         module_report++;
  450.         case 'n':            /* Look for specified module */
  451.         save_module (optarg);
  452.         break;
  453.         case 'p':            /* For specified directory */
  454.         save_file (optarg, "", (char *) NULL);
  455.         break;
  456.         case 'r':            /* Since specified Tag/Rev */
  457.         if (since_date || *since_tag || *backto)
  458.         {
  459.             error (0, 0, "rev overriding date/tag/backto");
  460.             *since_tag = *backto = '\0';
  461.             since_date = 0;
  462.         }
  463.         (void) strcpy (since_rev, optarg);
  464.         break;
  465.         case 't':            /* Since specified Tag/Rev */
  466.         if (since_date || *since_rev || *backto)
  467.         {
  468.             error (0, 0, "tag overriding date/marker/file/repos");
  469.             *since_rev = *backto = '\0';
  470.             since_date = 0;
  471.         }
  472.         (void) strcpy (since_tag, optarg);    /* tag */
  473.         break;
  474.         case 'u':            /* For specified username */
  475.         save_user (optarg);
  476.         break;
  477.         case 'x':
  478.         report_count++;
  479.         extract++;
  480.         {
  481.             char *cp;
  482.  
  483.             for (cp = optarg; *cp; cp++)
  484.             if (!index (ALL_REC_TYPES, *cp))
  485.                 error (1, 0, "%c is not a valid report type", cp);
  486.         }
  487.         (void) strcpy (rec_types, optarg);
  488.         break;
  489.         case '?':
  490.         default:
  491.         usage (history_usg);
  492.         break;
  493.     }
  494.     }
  495.     c = optind;                /* Save the handled option count */
  496.  
  497.     /* ================ Now analyze the arguments a bit */
  498.     if (!report_count)
  499.     v_checkout++;
  500.     else if (report_count > 1)
  501.     error (1, 0, "Only one report type allowed from: \"-Tcomx\".");
  502.  
  503.     if (all_users)
  504.     save_user ("");
  505.  
  506.     if (mod_list)
  507.     expand_modules ();
  508.  
  509.     if (tag_report)
  510.     {
  511.     if (!index (rec_types, 'T'))
  512.         (void) strcat (rec_types, "T");
  513.     }
  514.     else if (extract)
  515.     {
  516.     if (user_list)
  517.         user_sort++;
  518.     }
  519.     else if (modified)
  520.     {
  521.     (void) strcpy (rec_types, "MAR");
  522.     /*
  523.      * If the user has not specified a date oriented flag ("Since"), sort
  524.      * by Repository/file before date.  Default is "just" date.
  525.      */
  526.     if (!since_date && !*since_rev && !*since_tag && !*backto)
  527.     {
  528.         repos_sort++;
  529.         file_sort++;
  530.         /*
  531.          * If we are not looking for last_modified and the user specified
  532.          * one or more users to look at, sort by user before filename.
  533.          */
  534.         if (!last_entry && user_list)
  535.         user_sort++;
  536.     }
  537.     }
  538.     else if (module_report)
  539.     {
  540.     (void) strcpy (rec_types, last_entry ? "OMAR" : ALL_REC_TYPES);
  541.     module_sort++;
  542.     repos_sort++;
  543.     file_sort++;
  544.     working = 0;            /* User's workdir doesn't count here */
  545.     }
  546.     else
  547.     /* Must be "checkout" or default */
  548.     {
  549.     (void) strcpy (rec_types, "OF");
  550.     /* See comments in "modified" above */
  551.     if (!last_entry && user_list)
  552.         user_sort++;
  553.     if (!since_date && !*since_rev && !*since_tag && !*backto)
  554.         file_sort++;
  555.     }
  556.  
  557.     /* If no users were specified, use self (-a saves a universal ("") user) */
  558.     if (!user_list)
  559.     save_user (getcaller ());
  560.  
  561.     /* If we're looking back to a Tag value, must consider "Tag" records */
  562.     if (*since_tag && !index (rec_types, 'T'))
  563.     (void) strcat (rec_types, "T");
  564.  
  565.     argc -= c;
  566.     argv += c;
  567.     for (i = 0; i < argc; i++)
  568.     save_file ("", argv[i], (char *) NULL);
  569.  
  570.     if (histfile)
  571.     (void) strcpy (fname, histfile);
  572.     else
  573.     (void) sprintf (fname, "%s/%s/%s", CVSroot,
  574.             CVSROOTADM, CVSROOTADM_HISTORY);
  575.  
  576.     read_hrecs (fname);
  577.     qsort ((PTR) hrec_head, hrec_count, sizeof (struct hrec), sort_order);
  578.     report_hrecs ();
  579.  
  580.     return (0);
  581. }
  582.  
  583. void
  584. history_write (type, update_dir, revs, name, repository)
  585.     int type;
  586.     char *update_dir;
  587.     char *revs;
  588.     char *name;
  589.     char *repository;
  590. {
  591.     char fname[PATH_MAX], workdir[PATH_MAX], homedir[PATH_MAX];
  592.     static char username[20];        /* !!! Should be global */
  593.     FILE *fp;
  594.     char *slash = "", *cp, *cp2, *repos;
  595.     int i;
  596.     static char *tilde = "";
  597.     static char *PrCurDir = NULL;
  598.  
  599.     if (logoff)            /* History is turned off by cmd line switch */
  600.     return;
  601.     (void) sprintf (fname, "%s/%s/%s", CVSroot, CVSROOTADM, CVSROOTADM_HISTORY);
  602.  
  603.     /* turn off history logging if the history file does not exist */
  604.     if (!isfile (fname))
  605.     {
  606.     logoff = 1;
  607.     return;
  608.     }
  609.  
  610.     if (!(fp = Fopen (fname, "a")))    /* Some directory not there! */
  611.     return;
  612.  
  613.     repos = Short_Repository (repository);
  614.  
  615.     if (!PrCurDir)
  616.     {
  617.     struct passwd *pw;
  618.  
  619.     (void) strcpy (username, getcaller ());
  620.     PrCurDir = CurDir;
  621.     if (!(pw = (struct passwd *) getpwnam (username)))
  622.         error (0, 0, "cannot find own username");
  623.     else
  624.     {
  625.         /* Assumes neither CurDir nor pw->pw_dir ends in '/' */
  626.         i = strlen (pw->pw_dir);
  627.         if (!strncmp (CurDir, pw->pw_dir, i))
  628.         {
  629.         PrCurDir += i;        /* Point to '/' separator */
  630.         tilde = "~";
  631.         }
  632.         else
  633.         {
  634.         /* Try harder to find a "homedir" */
  635.         if (!getwd (workdir))
  636.             error (1, errno, "can't getwd in history");
  637.         if (chdir (pw->pw_dir) < 0)
  638.             error (1, errno, "can't chdir(%s)", pw->pw_dir);
  639.         if (!getwd (homedir))
  640.             error (1, errno, "can't getwd in:", pw->pw_dir);
  641.         (void) chdir (workdir);
  642.  
  643.         i = strlen (homedir);
  644.         if (!strncmp (CurDir, homedir, i))
  645.         {
  646.             PrCurDir += i;    /* Point to '/' separator */
  647.             tilde = "~";
  648.         }
  649.         }
  650.     }
  651.     }
  652.  
  653.     if (type == 'T')
  654.     {
  655.     repos = update_dir;
  656.     update_dir = "";
  657.     }
  658.     else if (update_dir && *update_dir)
  659.     slash = "/";
  660.     else
  661.     update_dir = "";
  662.  
  663.     (void) sprintf (workdir, "%s%s%s%s", tilde, PrCurDir, slash, update_dir);
  664.  
  665.     /*
  666.      * "workdir" is the directory where the file "name" is. ("^~" == $HOME)
  667.      * "repos"    is the Repository, relative to $CVSROOT where the RCS file is.
  668.      *
  669.      * "$workdir/$name" is the working file name.
  670.      * "$CVSROOT/$repos/$name,v" is the RCS file in the Repository.
  671.      *
  672.      * First, note that the history format was intended to save space, not
  673.      * to be human readable.
  674.      *
  675.      * The working file directory ("workdir") and the Repository ("repos")
  676.      * usually end with the same one or more directory elements.  To avoid
  677.      * duplication (and save space), the "workdir" field ends with
  678.      * an integer offset into the "repos" field.  This offset indicates the
  679.      * beginning of the "tail" of "repos", after which all characters are
  680.      * duplicates.
  681.      *
  682.      * In other words, if the "workdir" field has a '*' (a very stupid thing
  683.      * to put in a filename) in it, then every thing following the last '*'
  684.      * is a hex offset into "repos" of the first character from "repos" to
  685.      * append to "workdir" to finish the pathname.
  686.      *
  687.      * It might be easier to look at an example:
  688.      *
  689.      *  M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
  690.      *
  691.      * Indicates that the workdir is really "~/work/cvs/examples", saving
  692.      * 10 characters, where "~/work*d" would save 6 characters and mean that
  693.      * the workdir is really "~/work/examples".  It will mean more on
  694.      * directories like: usr/local/gnu/emacs/dist-19.17/lisp/term
  695.      *
  696.      * "workdir" is always an absolute pathname (~/xxx is an absolute path)
  697.      * "repos" is always a relative pathname.  So we can assume that we will
  698.      * never run into the top of "workdir" -- there will always be a '/' or
  699.      * a '~' at the head of "workdir" that is not matched by anything in
  700.      * "repos".  On the other hand, we *can* run off the top of "repos".
  701.      *
  702.      * Only "compress" if we save characters.
  703.      */
  704.  
  705.     if (!repos)
  706.     repos = "";
  707.  
  708.     cp = workdir + strlen (workdir) - 1;
  709.     cp2 = repos + strlen (repos) - 1;
  710.     for (i = 0; cp2 >= repos && cp > workdir && *cp == *cp2--; cp--)
  711.     i++;
  712.  
  713.     if (i > 2)
  714.     {
  715.     i = strlen (repos) - i;
  716.     (void) sprintf ((cp + 1), "*%x", i);
  717.     }
  718.  
  719.     if (fprintf (fp, "%c%08x|%s|%s|%s|%s|%s\n", type, time ((time_t *) NULL),
  720.          username, workdir, repos, revs ? revs : "", name) == EOF)
  721.     error (1, errno, "cannot write to history file: %s", fname);
  722.     (void) fclose (fp);
  723. }
  724.  
  725. /*
  726.  * save_user() adds a user name to the user list to select.  Zero-length
  727.  *        username ("") matches any user.
  728.  */
  729. static void
  730. save_user (name)
  731.     char *name;
  732. {
  733.     if (user_count == user_max)
  734.     {
  735.     user_max += USER_INCREMENT;
  736.     user_list = (char **) xrealloc ((char *) user_list,
  737.                     (int) user_max * sizeof (char *));
  738.     }
  739.     user_list[user_count++] = xstrdup (name);
  740. }
  741.  
  742. /*
  743.  * save_file() adds file name and associated module to the file list to select.
  744.  *
  745.  * If "dir" is null, store a file name as is.
  746.  * If "name" is null, store a directory name with a '*' on the front.
  747.  * Else, store concatenated "dir/name".
  748.  *
  749.  * Later, in the "select" stage:
  750.  *    - if it starts with '*', it is prefix-matched against the repository.
  751.  *    - if it has a '/' in it, it is matched against the repository/file.
  752.  *    - else it is matched against the file name.
  753.  */
  754. static void
  755. save_file (dir, name, module)
  756.     char *dir;
  757.     char *name;
  758.     char *module;
  759. {
  760.     char *cp;
  761.     struct file_list_str *fl;
  762.  
  763.     if (file_count == file_max)
  764.     {
  765.     file_max += FILE_INCREMENT;
  766.     file_list = (struct file_list_str *) xrealloc ((char *) file_list,
  767.                            file_max * sizeof (*fl));
  768.     }
  769.     fl = &file_list[file_count++];
  770.     fl->l_file = cp = xmalloc (strlen (dir) + strlen (name) + 2);
  771.     fl->l_module = module;
  772.  
  773.     if (dir && *dir)
  774.     {
  775.     if (name && *name)
  776.     {
  777.         (void) strcpy (cp, dir);
  778.         (void) strcat (cp, "/");
  779.         (void) strcat (cp, name);
  780.     }
  781.     else
  782.     {
  783.         *cp++ = '*';
  784.         (void) strcpy (cp, dir);
  785.     }
  786.     }
  787.     else
  788.     {
  789.     if (name && *name)
  790.     {
  791.         (void) strcpy (cp, name);
  792.     }
  793.     else
  794.     {
  795.         error (0, 0, "save_file: null dir and file name");
  796.     }
  797.     }
  798. }
  799.  
  800. static void
  801. save_module (module)
  802.     char *module;
  803. {
  804.     if (mod_count == mod_max)
  805.     {
  806.     mod_max += MODULE_INCREMENT;
  807.     mod_list = (char **) xrealloc ((char *) mod_list,
  808.                        mod_max * sizeof (char *));
  809.     }
  810.     mod_list[mod_count++] = xstrdup (module);
  811. }
  812.  
  813. static void
  814. expand_modules ()
  815. {
  816. }
  817.  
  818. /* fill_hrec
  819.  *
  820.  * Take a ptr to 7-part history line, ending with a newline, for example:
  821.  *
  822.  *    M273b3463|dgg|~/work*9|usr/local/cvs/examples|1.2|loginfo
  823.  *
  824.  * Split it into 7 parts and drop the parts into a "struct hrec".
  825.  * Return a pointer to the character following the newline.
  826.  */
  827.  
  828. #define NEXT_BAR(here) do { while (isspace(*line)) line++; hr->here = line; while ((c = *line++) && c != '|') ; if (!c) return(rtn); *(line - 1) = '\0'; } while (0)
  829.  
  830. static char *
  831. fill_hrec (line, hr)
  832.     char *line;
  833.     struct hrec *hr;
  834. {
  835.     char *cp, *rtn;
  836.     int c;
  837.     int off;
  838.     static int idx = 0;
  839.  
  840.     bzero ((char *) hr, sizeof (*hr));
  841.     while (isspace (*line))
  842.     line++;
  843.     if (!(rtn = index (line, '\n')))
  844.     return ("");
  845.     *rtn++ = '\0';
  846.  
  847.     hr->type = line++;
  848.     (void) sscanf (line, "%x", &hr->date);
  849.     while (*line && index ("0123456789abcdefABCDEF", *line))
  850.     line++;
  851.     if (*line == '\0')
  852.     return (rtn);
  853.  
  854.     line++;
  855.     NEXT_BAR (user);
  856.     NEXT_BAR (dir);
  857.     if ((cp = rindex (hr->dir, '*')) != NULL)
  858.     {
  859.     *cp++ = '\0';
  860.     (void) sscanf (cp, "%x", &off);
  861.     hr->end = line + off;
  862.     }
  863.     else
  864.     hr->end = line - 1;        /* A handy pointer to '\0' */
  865.     NEXT_BAR (repos);
  866.     NEXT_BAR (rev);
  867.     hr->idx = idx++;
  868.     if (index ("FOT", *(hr->type)))
  869.     hr->mod = line;
  870.  
  871.     NEXT_BAR (file);    /* This returns ptr to next line or final '\0' */
  872.     return (rtn);    /* If it falls through, go on to next record */
  873. }
  874.  
  875. /* read_hrecs's job is to read the history file and fill in all the "hrec"
  876.  * (history record) array elements with the ones we need to print.
  877.  *
  878.  * Logic:
  879.  * - Read the whole history file into a single buffer.
  880.  * - Walk through the buffer, parsing lines out of the buffer.
  881.  *   1. Split line into pointer and integer fields in the "next" hrec.
  882.  *   2. Apply tests to the hrec to see if it is wanted.
  883.  *   3. If it *is* wanted, bump the hrec pointer down by one.
  884.  */
  885. static void
  886. read_hrecs (fname)
  887.     char *fname;
  888. {
  889.     char *cp, *cp2;
  890.     int i, fd;
  891.     struct hrec *hr;
  892.     struct stat st_buf;
  893.  
  894.     if ((fd = open (fname, O_RDONLY)) < 0)
  895.     error (1, errno, "cannot open history file: %s", fname);
  896.  
  897.     if (fstat (fd, &st_buf) < 0)
  898.     error (1, errno, "can't stat history file");
  899.  
  900.     /* Exactly enough space for lines data */
  901.     if (!(i = st_buf.st_size))
  902.     error (1, 0, "history file is empty");
  903.     histdata = cp = xmalloc (i + 2);
  904.     histsize = i;
  905.  
  906.     if (read (fd, cp, i) != i)
  907.     error (1, errno, "cannot read log file");
  908.     (void) close (fd);
  909.  
  910.     if (*(cp + i - 1) != '\n')
  911.     {
  912.     *(cp + i) = '\n';        /* Make sure last line ends in '\n' */
  913.     i++;
  914.     }
  915.     *(cp + i) = '\0';
  916.     for (cp2 = cp; cp2 - cp < i; cp2++)
  917.     {
  918.     if (*cp2 != '\n' && !isprint (*cp2))
  919.         *cp2 = ' ';
  920.     }
  921.  
  922.     hrec_max = HREC_INCREMENT;
  923.     hrec_head = (struct hrec *) xmalloc (hrec_max * sizeof (struct hrec));
  924.  
  925.     while (*cp)
  926.     {
  927.     if (hrec_count == hrec_max)
  928.     {
  929.         struct hrec *old_head = hrec_head;
  930.  
  931.         hrec_max += HREC_INCREMENT;
  932.         hrec_head = (struct hrec *) xrealloc ((char *) hrec_head,
  933.                        hrec_max * sizeof (struct hrec));
  934.         if (hrec_head != old_head)
  935.         {
  936.         if (last_since_tag)
  937.             last_since_tag = hrec_head + (last_since_tag - old_head);
  938.         if (last_backto)
  939.             last_backto = hrec_head + (last_backto - old_head);
  940.         }
  941.     }
  942.  
  943.     hr = hrec_head + hrec_count;
  944.     cp = fill_hrec (cp, hr); /* cp == next line or '\0' at end of buffer */
  945.  
  946.     if (select_hrec (hr))
  947.         hrec_count++;
  948.     }
  949.  
  950.     /* Special selection problem: If "since_tag" is set, we have saved every
  951.      * record from the 1st occurrence of "since_tag", when we want to save
  952.      * records since the *last* occurrence of "since_tag".  So what we have
  953.      * to do is bump hrec_head forward and reduce hrec_count accordingly.
  954.      */
  955.     if (last_since_tag)
  956.     {
  957.     hrec_count -= (last_since_tag - hrec_head);
  958.     hrec_head = last_since_tag;
  959.     }
  960.  
  961.     /* Much the same thing is necessary for the "backto" option. */
  962.     if (last_backto)
  963.     {
  964.     hrec_count -= (last_backto - hrec_head);
  965.     hrec_head = last_backto;
  966.     }
  967. }
  968.  
  969. /* Utility program for determining whether "find" is inside "string" */
  970. static int
  971. within (find, string)
  972.     char *find, *string;
  973. {
  974.     int c, len;
  975.  
  976.     if (!find || !string)
  977.     return (0);
  978.  
  979.     c = *find++;
  980.     len = strlen (find);
  981.  
  982.     while (*string)
  983.     {
  984.     if (!(string = index (string, c)))
  985.         return (0);
  986.     string++;
  987.     if (!strncmp (find, string, len))
  988.         return (1);
  989.     }
  990.     return (0);
  991. }
  992.  
  993. /* The purpose of "select_hrec" is to apply the selection criteria based on
  994.  * the command arguments and defaults and return a flag indicating whether
  995.  * this record should be remembered for printing.
  996.  */
  997. static int
  998. select_hrec (hr)
  999.     struct hrec *hr;
  1000. {
  1001.     char **cpp, *cp, *cp2;
  1002.     struct file_list_str *fl;
  1003.     int count;
  1004.  
  1005.     /* "Since" checking:  The argument parser guarantees that only one of the
  1006.      *              following four choices is set:
  1007.      *
  1008.      * 1. If "since_date" is set, it contains a Unix time_t specified on the
  1009.      *    command line. hr->date fields earlier than "since_date" are ignored.
  1010.      * 2. If "since_rev" is set, it contains either an RCS "dotted" revision
  1011.      *    number (which is of limited use) or a symbolic TAG.  Each RCS file
  1012.      *    is examined and the date on the specified revision (or the revision
  1013.      *    corresponding to the TAG) in the RCS file (CVSROOT/repos/file) is
  1014.      *    compared against hr->date as in 1. above.
  1015.      * 3. If "since_tag" is set, matching tag records are saved.  The field
  1016.      *    "last_since_tag" is set to the last one of these.  Since we don't
  1017.      *    know where the last one will be, all records are saved from the
  1018.      *    first occurrence of the TAG.  Later, at the end of "select_hrec"
  1019.      *    records before the last occurrence of "since_tag" are skipped.
  1020.      * 4. If "backto" is set, all records with a module name or file name
  1021.      *    matching "backto" are saved.  In addition, all records with a
  1022.      *    repository field with a *prefix* matching "backto" are saved.
  1023.      *    The field "last_backto" is set to the last one of these.  As in
  1024.      *    3. above, "select_hrec" adjusts to include the last one later on.
  1025.      */
  1026.     if (since_date)
  1027.     {
  1028.     if (hr->date < since_date)
  1029.         return (0);
  1030.     }
  1031.     else if (*since_rev)
  1032.     {
  1033.     Vers_TS *vers;
  1034.     time_t t;
  1035.  
  1036.     vers = Version_TS (hr->repos, (char *) NULL, since_rev, (char *) NULL,
  1037.                hr->file, 1, 0, (List *) NULL, (List *) NULL);
  1038.     if (vers->vn_rcs)
  1039.     {
  1040.         if ((t = RCS_getrevtime (vers->srcfile, vers->vn_rcs, (char *) 0, 0))
  1041.         != (time_t) 0)
  1042.         {
  1043.         if (hr->date < t)
  1044.         {
  1045.             freevers_ts (&vers);
  1046.             return (0);
  1047.         }
  1048.         }
  1049.     }
  1050.     freevers_ts (&vers);
  1051.     }
  1052.     else if (*since_tag)
  1053.     {
  1054.     if (*(hr->type) == 'T')
  1055.     {
  1056.         /*
  1057.          * A 'T'ag record, the "rev" field holds the tag to be set,
  1058.          * while the "repos" field holds "D"elete, "A"dd or a rev.
  1059.          */
  1060.         if (within (since_tag, hr->rev))
  1061.         {
  1062.         last_since_tag = hr;
  1063.         return (1);
  1064.         }
  1065.         else
  1066.         return (0);
  1067.     }
  1068.     if (!last_since_tag)
  1069.         return (0);
  1070.     }
  1071.     else if (*backto)
  1072.     {
  1073.     if (within (backto, hr->file) || within (backto, hr->mod) ||
  1074.         within (backto, hr->repos))
  1075.         last_backto = hr;
  1076.     else
  1077.         return (0);
  1078.     }
  1079.  
  1080.     /* User checking:
  1081.      *
  1082.      * Run down "user_list", match username ("" matches anything)
  1083.      * If "" is not there and actual username is not there, return failure.
  1084.      */
  1085.     if (user_list && hr->user)
  1086.     {
  1087.     for (cpp = user_list, count = user_count; count; cpp++, count--)
  1088.     {
  1089.         if (!**cpp)
  1090.         break;            /* null user == accept */
  1091.         if (!strcmp (hr->user, *cpp))    /* found listed user */
  1092.         break;
  1093.     }
  1094.     if (!count)
  1095.         return (0);            /* Not this user */
  1096.     }
  1097.  
  1098.     /* Record type checking:
  1099.      *
  1100.      * 1. If Record type is not in rec_types field, skip it.
  1101.      * 2. If mod_list is null, keep everything.  Otherwise keep only modules
  1102.      *    on mod_list.
  1103.      * 3. If neither a 'T', 'F' nor 'O' record, run through "file_list".  If
  1104.      *    file_list is null, keep everything.  Otherwise, keep only files on
  1105.      *    file_list, matched appropriately.
  1106.      */
  1107.     if (!index (rec_types, *(hr->type)))
  1108.     return (0);
  1109.     if (!index ("TFO", *(hr->type)))    /* Don't bother with "file" if "TFO" */
  1110.     {
  1111.     if (file_list)            /* If file_list is null, accept all */
  1112.     {
  1113.         for (fl = file_list, count = file_count; count; fl++, count--)
  1114.         {
  1115.         /* 1. If file_list entry starts with '*', skip the '*' and
  1116.          *    compare it against the repository in the hrec.
  1117.          * 2. If file_list entry has a '/' in it, compare it against
  1118.          *    the concatenation of the repository and file from hrec.
  1119.          * 3. Else compare the file_list entry against the hrec file.
  1120.          */
  1121.         char cmpfile[PATH_MAX];
  1122.  
  1123.         if (*(cp = fl->l_file) == '*')
  1124.         {
  1125.             cp++;
  1126.             /* if argument to -p is a prefix of repository */
  1127.             if (!strncmp (cp, hr->repos, strlen (cp)))
  1128.             {
  1129.             hr->mod = fl->l_module;
  1130.             break;
  1131.             }
  1132.         }
  1133.         else
  1134.         {
  1135.             if (index (cp, '/'))
  1136.             {
  1137.             (void) sprintf (cp2 = cmpfile, "%s/%s",
  1138.                     hr->repos, hr->file);
  1139.             }
  1140.             else
  1141.             {
  1142.             cp2 = hr->file;
  1143.             }
  1144.  
  1145.             /* if requested file is found within {repos}/file fields */
  1146.             if (within (cp, cp2))
  1147.             {
  1148.             hr->mod = fl->l_module;
  1149.             break;
  1150.             }
  1151.         }
  1152.         }
  1153.         if (!count)
  1154.         return (0);        /* String specified and no match */
  1155.     }
  1156.     }
  1157.     if (mod_list)
  1158.     {
  1159.     for (cpp = mod_list, count = mod_count; count; cpp++, count--)
  1160.     {
  1161.         if (hr->mod && !strcmp (hr->mod, *cpp))    /* found module */
  1162.         break;
  1163.     }
  1164.     if (!count)
  1165.         return (0);    /* Module specified & this record is not one of them. */
  1166.     }
  1167.  
  1168.     return (1);        /* Select this record unless rejected above. */
  1169. }
  1170.  
  1171. /* The "sort_order" routine (when handed to qsort) has arranged for the
  1172.  * hrecs files to be in the right order for the report.
  1173.  *
  1174.  * Most of the "selections" are done in the select_hrec routine, but some
  1175.  * selections are more easily done after the qsort by "accept_hrec".
  1176.  */
  1177. static void
  1178. report_hrecs ()
  1179. {
  1180.     struct hrec *hr, *lr;
  1181.     struct tm *tm;
  1182.     int i, count, ty;
  1183.     char *cp;
  1184.     int user_len, file_len, rev_len, mod_len, repos_len;
  1185.  
  1186.     if (*since_tag && !last_since_tag)
  1187.     {
  1188.     (void) printf ("No tag found: %s\n", since_tag);
  1189.     return;
  1190.     }
  1191.     else if (*backto && !last_backto)
  1192.     {
  1193.     (void) printf ("No module, file or repository with: %s\n", backto);
  1194.     return;
  1195.     }
  1196.     else if (hrec_count < 1)
  1197.     {
  1198.     (void) printf ("No records selected.\n");
  1199.     return;
  1200.     }
  1201.  
  1202.     user_len = file_len = rev_len = mod_len = repos_len = 0;
  1203.  
  1204.     /* Run through lists and find maximum field widths */
  1205.     hr = lr = hrec_head;
  1206.     hr++;
  1207.     for (count = hrec_count; count--; lr = hr, hr++)
  1208.     {
  1209.     char repos[PATH_MAX];
  1210.  
  1211.     if (!count)
  1212.         hr = NULL;
  1213.     if (!accept_hrec (lr, hr))
  1214.         continue;
  1215.  
  1216.     ty = *(lr->type);
  1217.     (void) strcpy (repos, lr->repos);
  1218.     if ((cp = rindex (repos, '/')) != NULL)
  1219.     {
  1220.         if (lr->mod && !strcmp (++cp, lr->mod))
  1221.         {
  1222.         (void) strcpy (cp, "*");
  1223.         }
  1224.     }
  1225.     if ((i = strlen (lr->user)) > user_len)
  1226.         user_len = i;
  1227.     if ((i = strlen (lr->file)) > file_len)
  1228.         file_len = i;
  1229.     if (ty != 'T' && (i = strlen (repos)) > repos_len)
  1230.         repos_len = i;
  1231.     if (ty != 'T' && (i = strlen (lr->rev)) > rev_len)
  1232.         rev_len = i;
  1233.     if (lr->mod && (i = strlen (lr->mod)) > mod_len)
  1234.         mod_len = i;
  1235.     }
  1236.  
  1237.     /* Walk through hrec array setting "lr" (Last Record) to each element.
  1238.      * "hr" points to the record following "lr" -- It is NULL in the last
  1239.      * pass.
  1240.      *
  1241.      * There are two sections in the loop below:
  1242.      * 1. Based on the report type (e.g. extract, checkout, tag, etc.),
  1243.      *    decide whether the record should be printed.
  1244.      * 2. Based on the record type, format and print the data.
  1245.      */
  1246.     for (lr = hrec_head, hr = (lr + 1); hrec_count--; lr = hr, hr++)
  1247.     {
  1248.     char workdir[PATH_MAX], repos[PATH_MAX];
  1249.  
  1250.     if (!hrec_count)
  1251.         hr = NULL;
  1252.     if (!accept_hrec (lr, hr))
  1253.         continue;
  1254.  
  1255.     ty = *(lr->type);
  1256.     tm = localtime (&(lr->date));
  1257.     (void) printf ("%c %02d/%02d %02d:%02d %-*s", ty, tm->tm_mon + 1,
  1258.           tm->tm_mday, tm->tm_hour, tm->tm_min, user_len, lr->user);
  1259.  
  1260.     (void) sprintf (workdir, "%s%s", lr->dir, lr->end);
  1261.     if ((cp = rindex (workdir, '/')) != NULL)
  1262.     {
  1263.         if (lr->mod && !strcmp (++cp, lr->mod))
  1264.         {
  1265.         (void) strcpy (cp, "*");
  1266.         }
  1267.     }
  1268.     (void) strcpy (repos, lr->repos);
  1269.     if ((cp = rindex (repos, '/')) != NULL)
  1270.     {
  1271.         if (lr->mod && !strcmp (++cp, lr->mod))
  1272.         {
  1273.         (void) strcpy (cp, "*");
  1274.         }
  1275.     }
  1276.  
  1277.     switch (ty)
  1278.     {
  1279.         case 'T':
  1280.         /* 'T'ag records: repository is a "tag type", rev is the tag */
  1281.         (void) printf (" %-*s [%s:%s]", mod_len, lr->mod, lr->rev,
  1282.                    repos);
  1283.         if (working)
  1284.             (void) printf (" {%s}", workdir);
  1285.         break;
  1286.         case 'F':
  1287.         case 'O':
  1288.         if (lr->rev && *(lr->rev))
  1289.             (void) printf (" [%s]", lr->rev);
  1290.         (void) printf (" %-*s =%s%-*s %s", repos_len, repos, lr->mod,
  1291.                    mod_len + 1 - strlen (lr->mod), "=", workdir);
  1292.         break;
  1293.         case 'W':
  1294.         case 'U':
  1295.         case 'C':
  1296.         case 'G':
  1297.         case 'M':
  1298.         case 'A':
  1299.         case 'R':
  1300.         (void) printf (" %-*s %-*s %-*s =%s= %s", rev_len, lr->rev,
  1301.                    file_len, lr->file, repos_len, repos,
  1302.                    lr->mod ? lr->mod : "", workdir);
  1303.         break;
  1304.         default:
  1305.         (void) printf ("Hey! What is this junk? RecType[0x%2.2x]", ty);
  1306.         break;
  1307.     }
  1308.     (void) putchar ('\n');
  1309.     }
  1310. }
  1311.  
  1312. static int
  1313. accept_hrec (lr, hr)
  1314.     struct hrec *hr, *lr;
  1315. {
  1316.     int ty;
  1317.  
  1318.     ty = *(lr->type);
  1319.  
  1320.     if (last_since_tag && ty == 'T')
  1321.     return (1);
  1322.  
  1323.     if (v_checkout)
  1324.     {
  1325.     if (ty != 'O')
  1326.         return (0);            /* Only interested in 'O' records */
  1327.  
  1328.     /* We want to identify all the states that cause the next record
  1329.      * ("hr") to be different from the current one ("lr") and only
  1330.      * print a line at the allowed boundaries.
  1331.      */
  1332.  
  1333.     if (!hr ||            /* The last record */
  1334.         strcmp (hr->user, lr->user) ||    /* User has changed */
  1335.         strcmp (hr->mod, lr->mod) ||/* Module has changed */
  1336.         (working &&            /* If must match "workdir" */
  1337.          (strcmp (hr->dir, lr->dir) ||    /*    and the 1st parts or */
  1338.           strcmp (hr->end, lr->end))))    /*    the 2nd parts differ */
  1339.  
  1340.         return (1);
  1341.     }
  1342.     else if (modified)
  1343.     {
  1344.     if (!last_entry ||        /* Don't want only last rec */
  1345.         !hr ||            /* Last entry is a "last entry" */
  1346.         strcmp (hr->repos, lr->repos) ||    /* Repository has changed */
  1347.         strcmp (hr->file, lr->file))/* File has changed */
  1348.         return (1);
  1349.  
  1350.     if (working)
  1351.     {                /* If must match "workdir" */
  1352.         if (strcmp (hr->dir, lr->dir) ||    /*    and the 1st parts or */
  1353.         strcmp (hr->end, lr->end))    /*    the 2nd parts differ */
  1354.         return (1);
  1355.     }
  1356.     }
  1357.     else if (module_report)
  1358.     {
  1359.     if (!last_entry ||        /* Don't want only last rec */
  1360.         !hr ||            /* Last entry is a "last entry" */
  1361.         strcmp (hr->mod, lr->mod) ||/* Module has changed */
  1362.         strcmp (hr->repos, lr->repos) ||    /* Repository has changed */
  1363.         strcmp (hr->file, lr->file))/* File has changed */
  1364.         return (1);
  1365.     }
  1366.     else
  1367.     {
  1368.     /* "extract" and "tag_report" always print selected records. */
  1369.     return (1);
  1370.     }
  1371.  
  1372.     return (0);
  1373. }
  1374.